import { Device, ActionType, TouchMeta, Banner } from "../serve/interfaces"
import { setSender, showCommand, hideCommand, CommandItem, defaultCommands } from './command'
                            
export { ActionType, defaultCommands, CommandItem }

export interface STFEaseClientProps {
    /** ws连接 */
    ws: WebSocket
    /** 改写send */
    sender?: WebSocket['send']
    /** 播放使用的canvas */
    canvas: HTMLCanvasElement
    /** 
     * 初始化canvas2d的参数
     * @default options { alpha: false }
    */
    options?: CanvasRenderingContext2DSettings
    /**
     * canvas 展示高度
     * 宽度会根据设备计算出来，建议居中展示
     */
    height?: number
    /**
     * 自定义菜单命令
     */
    commands?: CommandItem[]
    /**
     * ws连接初始化完成
     */
    onWSReady?: (ws: WebSocket) => void
    /**
     * 服务端发送截图banner信息
     */
    onBanner?: (info?: Banner) => void
    /**
     * 服务端发送设备基础信息
     */
    onDevice?: (device?: Device) => void
    /**
     * 服务端发送minitouch连接信息
     */
    onTouchMeta?: (meta?: TouchMeta) => void
    /**
     * 接收到图片
     */
    onFrame?: (frame?: Blob) => void
    /**
     * 服务端发送错误信息
     */
    onErrorInfo?: (err?: string) => void
    /**
     * ws关闭
    */
    onClose?: () => void
    /**
     * 不开启操作
     */
    readonly?: boolean
}
export default ({
    ws,
    canvas,
    options = { alpha: false },
    height = 780,
    commands = defaultCommands,
    onBanner,
    onDevice,
    onTouchMeta,
    onFrame,
    onErrorInfo = alert,
    onClose,
    sender,
    readonly,
}: STFEaseClientProps) => {
    const g = canvas.getContext('2d', options)
    sender = sender || ws.send.bind(ws)
    setSender(sender, commands)
    ws.binaryType = 'blob'

    let banner: Banner
    let touch_meta: TouchMeta
    let device: Device
    const onMessage = function (e: MessageEvent) {
        if (typeof e.data === 'string') {
            const [a, type, data] = e.data.match(/^(\w+)\:(.*?)$/);
            switch (type as ActionType) {
                case ActionType.BANNER:
                    banner = JSON.parse(data);
                    onBanner && onBanner(banner)
                    canvas.width = banner.realWidth * height / banner.realHeight
                    break;
                case ActionType.DEVICE:
                    device = JSON.parse(data)
                    onDevice && onDevice(device)
                    break;
                case ActionType.TOUCH_META:
                    touch_meta = JSON.parse(data)
                    onTouchMeta && onTouchMeta(touch_meta)
                    break;
                case ActionType.ERROR:
                    onErrorInfo(data)
                    break;
            }
            return
        }
        if (!banner) {
            return
        }

        let blob = new Blob([e.data], { type: 'image/jpeg' })
        onFrame && onFrame(blob)
        let img = new Image()
        img.onload = function () {
            canvas.width = banner.realWidth * height / banner.realHeight;
            canvas.height = height;
            g.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
            img.onload = null
            delete img.src
            blob = null
        }
        img.src = URL.createObjectURL(blob)
    }
    let init_timer: NodeJS.Timer
    let init_timer_interval = 1
    let onOpen = function onOpen () {
        if (!device) {
            sender(`${ActionType.BANNER}: `)
            sender(`${ActionType.DEVICE}: `)
            sender(`${ActionType.TOUCH_META}: `)
        }
        init_timer = setTimeout(onOpen, init_timer_interval * 1000)
        init_timer_interval = 3
    }
    ws.addEventListener('message', onMessage)
    ws.addEventListener('close', function () {
        destory(),
        onClose && onClose()
    })
    ws.addEventListener('open', onOpen)

    let is_mousedown = false
    let mousedown: {x: number, y: number, t: number}
    let mousemove: {x: number, y: number, t: number}
    let mouseover = false
    const onMouseOver = function () {
        mouseover = true
    }
    const onKeyUp = function (e: KeyboardEvent) {
        const keymap = {
            'alt': 57,
            'shift': 59,
            'tab': 61,
            ' ': 62,
            'enter': 66,
            'backspace': 67,
        }
        if (mouseover) {
            let k = e.key.toLowerCase()
            if (keymap[k]) {
                sender(`${ActionType.COMMAND}: input keyevent ${keymap[k]}`)
            } else if (e.key.length === 1) {
                sender(`${ActionType.COMMAND}: input text "${e.key}"`)
            }
            e.preventDefault()
            e.stopPropagation()
        }
    }
    const onMouseDown = function (e: MouseEvent) {
        const { offsetX, offsetY, which } = e
        if (readonly || which === 3 || !banner) return
        is_mousedown = true

        if (e.target === canvas) {
            const x = (offsetX * banner.realWidth / canvas.width) >> 0
            const y = (offsetY * banner.realHeight / canvas.height) >> 0
            mousedown = {
                x, y, t: Date.now()
            }
            if (touch_meta) {
                sender(`${ActionType.TOUCH}: d 0 ${x} ${y} ${touch_meta.max_pressure}`)
            }
        }
    }
    const onMouseMove = function (e: MouseEvent) {
        const { offsetX, offsetY } = e
        if (!readonly && is_mousedown && banner) {
            const x = (offsetX * banner.realWidth / canvas.width) >> 0
            const y = (offsetY * banner.realHeight / canvas.height) >> 0
            if (touch_meta) {
                sender(`${ActionType.TOUCH}: ${ mousedown ? 'm' : 'd' } 0 ${x} ${y} ${touch_meta.max_pressure}`)
            }
            mousemove = {
                x, y, t: Date.now()
            }
            if (!mousedown) {
                mousedown = mousemove
            }
        }
    }
    const onMouseUp = function () {
        if (is_mousedown && banner && mousedown) {
            if (touch_meta) {
                sender(`${ActionType.TOUCH}: u 0`)
            } else {
                mousemove = mousemove || { ...mousedown, t: Date.now () }
                sender(`${ActionType.COMMAND}: input swipe ${mousedown.x} ${mousedown.y} ${mousemove.x} ${mousemove.y} ${mousemove.t - mousedown.t}`)
            }
        }
        is_mousedown = false
        mousedown = null
        mousemove = null
    }
    const onMouseLeave = function () {
        onMouseUp()
        mouseover = false
    }
    const onContextMenu = function (e: MouseEvent) {
        e.preventDefault();
        showCommand({ target: e.target as HTMLElement})
    }
    document.addEventListener('keydown', onKeyUp)
    document.addEventListener('mousedown', onMouseDown)
    canvas.addEventListener('mouseover', onMouseOver)
    canvas.addEventListener('mousemove', onMouseMove)
    document.addEventListener('mouseup', onMouseUp)
    canvas.addEventListener('mouseleave', onMouseLeave)
    canvas.addEventListener('contextmenu', onContextMenu)

    let wheel_timer: NodeJS.Timer;
    let isWheelRun = false;
    let delta = 0;
    const mousewheel = function(e) {
        const { offsetX, offsetY, deltaY } = e
        if (touch_meta && banner) {
            const x = (offsetX * banner.realWidth / canvas.width) >> 0
            const y = (offsetY * banner.realHeight / canvas.height) >> 0
            const _y = (deltaY > 0 ? 50 : -50) * banner.realHeight / canvas.height
            if (!isWheelRun) {
                isWheelRun = true
                sender(`${ActionType.TOUCH}: d 0 ${x} ${y} ${touch_meta.max_pressure}`)
            }
            delta += _y
            sender(`${ActionType.TOUCH}: m 0 ${x} ${y - delta} ${touch_meta.max_pressure}`)
            clearTimeout(wheel_timer)
            wheel_timer = setTimeout(() => {
                // sender(`${ActionType.TOUCH}: u 0`)
                isWheelRun = false
                delta = 0;
            }, 60);
            e.stopPropagation();
        }
    }
    canvas.addEventListener('wheel', mousewheel)

    const destory = function () {
        document.removeEventListener('keydown', onKeyUp)
        document.removeEventListener('mousedown', onMouseDown)
        canvas.removeEventListener('mouseover', onMouseOver)
        canvas.removeEventListener('mousemove', onMouseMove)
        document.removeEventListener('mouseup', onMouseUp)
        canvas.removeEventListener('mouseleave', onMouseLeave)
        canvas.removeEventListener('contextmenu', onContextMenu)
        canvas.removeEventListener('wheel', mousewheel)
        clearTimeout(init_timer)
        device = null
        banner = null
        touch_meta = null
        mouseover = false
        ws.close();
    }

    const updateHeight = (h: number) => {
        canvas.height = height = h
    }
    return {
        setSender: (_sender: WebSocket['send']) => {
            sender = _sender
            setSender(_sender)
        },
        showCommand,
        hideCommand,
        updateHeight,
        destory,
    }
}